import numpy as np
from sklearn.decomposition import PCA, FactorAnalysis, IncrementalPCA


def sklearn_fit(data, n_components=None):
    """
    sklearn的pca

    :param n_components:
    :param data:
    :return:
    """
    if not n_components:
        n_components = min(np.shape(data))

    sk_pca = PCA(n_components=n_components)
    sk_ipca = IncrementalPCA(n_components=n_components)
    sk_fa = FactorAnalysis(n_components=n_components)

    sk_pca_down_dimension_data = sk_pca.fit_transform(data)
    sk_ipca_down_dimension_data = sk_ipca.fit_transform(data)
    sk_fa_down_dimension_data = sk_fa.fit_transform(data)


def load_data(file, delim='\t'):
    """

    :param file:
    :param delim:
    :return:
    """
    with open(file, 'r') as f:
        file_data = f.readlines()

    arr = [info[: -1].strip().split(delim) for info in file_data]
    return [list(map(float, line)) for line in arr]


def pca(data_set, top_n_feat=9999999):
    """
    1、协方差：cov = sum((x-x_mean) * (y - y_mean)) / (n - 1) 分母考虑无偏估计
    2、协方差矩阵
      1）np.cov的ddof默认为None，即为1，为无偏估计，若ddof=0，均为有偏估计
      2）公式：协方差(i,j)=（第i列所有元素-第i列均值）*（第j列所有元素-第j列均值）/（样本数-1）

    :param data_set:
    :param top_n_feat:
    :return:
    """
    # 协方差矩阵
    data_no_mean = np.mat(data_set) - np.mean(data_set, axis=0)
    data_cov = np.cov(data_no_mean, rowvar=False)

    eig_vals, eig_vectors = np.linalg.eig(data_cov)
    # 特征值排序
    eig_vals_sort_ind = np.argsort(eig_vals)
    eig_vals_sort_ind = eig_vals_sort_ind[:-(top_n_feat + 1):-1]

    # 特征值分析
    eig_vals_sort = eig_vals[eig_vals_sort_ind]
    eig_vals_total = eig_vals.sum()
    percent_total = 0
    for num, eig_val in enumerate(eig_vals_sort):
        eig_val_ratio = 100 * eig_val / eig_vals_total
        percent_total += eig_val_ratio
        print('{} {} {}'.format(num, eig_val_ratio, percent_total))

    # 前n特征值对应的特征向量
    eig_vectors = eig_vectors[:, eig_vals_sort_ind]

    down_dimension_data = data_no_mean * np.mat(eig_vectors)

    # 数据还原
    re_data_set = (down_dimension_data * np.mat(eig_vectors).T) + np.mean(data_set, axis=0)

    return down_dimension_data, re_data_set


def replace_nan_mean(data):
    """
    将数据集中的nan替换为均值
    :param data:
    :return:
    """
    if not isinstance(data, np.ndarray):
        data = np.array(data)
    m, n = data.shape

    for num in range(n):
        info = data[:, num]
        # 获取非nan的下标
        non_nan_idx = np.nonzero(~np.isnan(info))[0]
        nan_idx = np.nonzero(np.isnan(info))[0]

        info[nan_idx] = info[non_nan_idx].mean()

    return data


def run_simple():
    top_n = 1

    arr = load_data(file='./data/B/chp13/testSet.txt')
    print(arr)
    down_dimension_data, re_data_set = pca(data_set=arr, top_n_feat=top_n)
    print(down_dimension_data.shape)

    # sklearn
    sklearn_fit(n_components=top_n, data=arr)


def run():
    secom_data = load_data(file='./data/B/chp13/secom.data', delim=' ')
    secom_data = np.array(secom_data)

    secom_data = replace_nan_mean(data=secom_data)
    pca(data_set=secom_data)

    # sklearn
    sklearn_fit(data=secom_data)


if __name__ == '__main__':
    # run_simple()
    run()
